--[[        TWIRL
            A collection of useful utility functions for WoW AddOns

            by Tabithy                                                     ]]

TWIRL_VERSION = "@DEV@"

-- Format is the same as for CSS/HTML. RGB, with R, G, B = 00 - FF hexadecimal
local COLORS = {
    black = "000000",
    silver = "C0C0C0",
    gray = "808080",
    white = "FFFFFF",
    maroon = "800000",
    red = "FF0000",
    purple = "800080",
    fuchsia ="FF00FF",
    green = "008000",
    lime = "00FF00",
    olive = "808000",
    yellow = "FFFF00",
    navy = "000080",
    blue = "0000FF",
    teal = "008080",
    aqua = "00FFFF",
    chartreuse = "7FFF00",
    salmon = "FF6666",
    cantaloupe = "FFCC66",
    lavender = "CC66FF",
    tangerine = "F29A3E",
    sky = "66CCFF",
    carnation = "FF6FCF",
    mint = "7EFF81"}


local SLOTS = { "HeadSlot", "NeckSlot", "ShoulderSlot", "BackSlot", 
    "ChestSlot", "ShirtSlot", "TabardSlot", "WristSlot", "HandsSlot", 
    "WaistSlot", "LegsSlot", "FeetSlot", "Finger0Slot", "Finger1Slot", 
    "Trinket0Slot", "Trinket1Slot", "MainHandSlot", "SecondaryHandSlot", 
    "RangedSlot", "AmmoSlot" }


-- A list of all valid units
local UNITS = {"target", "player", "party1", "party2", "party3", "party4",
    "pet", "partypet1", "partypet2", "partypet3", "partypet4", "mouseover"}
for i = 1, 40 do
    table.insert(UNITS, "raid" .. i)
    table.insert(UNITS,"raidpet" .. i)
end


local function decRGBColor(color)
    local r = math.floor(100 
            * tonumber(string.sub(color, 1, 2), 16) / 255) / 100
    local g = math.floor(100 
            * tonumber(string.sub(color, 3, 4), 16) / 255) / 100
    local b = math.floor(100 
            * tonumber(string.sub(color, 5, 6), 16) / 255) / 100
    return r, g, b
end



-- color format used by WoW for text is |CAARRGGBB with |C being a
-- required header, AA is alpha, RR is red, BB is blue, GG is green,
-- with each component being a two digit hexidecimal number.
-- ie. |CFFFFFFFF is black, |CFFFF0000 is red.  The string "|r" should
-- be used to close color fields.
local function colorize(message_text)
	table.foreach(COLORS, 
	        function(color, value) 
	            message_text = string.gsub(message_text, "<" .. color .. ">", 
	                    "|CFF" .. value)
	        end)
	message_text = string.gsub(message_text,"</>", "|r") -- close tag        	
	return message_text
end


local function printer(printColor, prefix)
    printColor = printColor or COLORS.white
    prefix = prefix or ""
    local function print_fn(message_text, color)
        local r, g, b = nil, nil, nil
        color = color or printColor
        r, g, b = decRGBColor(color)
        DEFAULT_CHAT_FRAME:AddMessage(colorize(prefix .. message_text), r, g, 
                b, 1)
    end
    return print_fn
end

local print = printer()


-- Create a function that will display an error message at the top of
-- the screen with the given color, prefix and hold time.  Will
-- attempt to use sct (scrolling combat text), if that add-on is
-- loaded.  IE local mes = twirl.messager(twirl.colors.red, "test: ", 1,
-- false) would give you a message function named mes that displays
-- text in red, with the prefix "test:", with a default 1s hold time,
-- and without using scrolling combat text.
local function messager(messageColor, prefix, holdTime, useSct)
    messageColor = messageColor or COLORS.white
    prefix = prefix or ""
    holdTime = holdTime or UIERRORS_HOLD_TIME -- 5s by default
    if (useSct == nil) then useSct = true end -- use sct by default
    local function message_fn(messageText, color, time)
        local red, green, blue = nil, nil, nil
        color = color or messageColor
        time = time or holdTime
        red, green, blue = decRGBColor(color)
        messageText = colorize(prefix .. messageText)
        if (SCT_Display and useSct) then
            SCT_Display(messageText,{r = red, g = green, b = blue})
        else 
            UIErrorsFrame:AddMessage(messageText, red, 
                green, blue, 1, time)
        end
    end
    return message_fn
end

local message = messager()


-- Returns true if action was found and cast, otherwise returns false
local function castPetActionByName(name)
    local index = 0
    local action 
    local cast = false
    repeat
        index = index + 1
        action = GetSpellName(index, "pet")
        if (name == action) then
            CastSpell(index, "pet")
            cast = true
            break
        end
    until (not action)
    return cast
end


-- from Discord Macro Functions CheckForDebuff (with modifications)
local function checkForBuff(unit, query)
    query = string.upper(query)
	local text
	for i = 1, 16 do
		if (UnitBuff(unit, i)) then
			Twirl_Tooltip:SetUnitBuff(unit, i)
			if (Twirl_TooltipTextLeft1:IsShown()) then
				text = string.upper(Twirl_TooltipTextLeft1:GetText())
				if (string.find(text, query, 1, true)) then
					return true
				end
			end
		else
			return false
		end
	end
end


-- from Discord Macro Functions CheckForDebuff (with modifications)
local function checkForBuffType(unit, query)
    query = string.upper(query)
	local text
	for i = 1, 16 do
		if (UnitBuff(unit, i)) then
			Twirl_Tooltip:SetUnitBuff(unit, i)
			if (Twirl_TooltipTextRight1:IsShown()) then
				text = string.upper(Twirl_TooltipTextRight1:GetText())
				if (string.find(text, query, 1, true)) then
					return true
				end
			end
		else
			return false
		end
	end
end


-- from Discord Macro Functions CheckForDebuff (with modifications)
local function checkForDebuff(unit, query)
    query = string.upper(query)
	local text
	for i = 1, 16 do
		if (UnitDebuff(unit, i)) then
			Twirl_Tooltip:SetUnitDebuff(unit, i)
			if (Twirl_TooltipTextLeft1:IsShown()) then
				text = string.upper(Twirl_TooltipTextLeft1:GetText())
				if (string.find(text, query, 1, true)) then
					return true
				end
			end
		else
			return false
		end
	end
end


-- from Discord Macro Functions CheckForDebuff (with modifications).
local function checkForDebuffType(unit, query)
	query = string.upper(query)
    local text
	for i = 1, 16 do
		if (UnitDebuff(unit, i)) then
			Twirl_Tooltip:SetUnitDebuff(unit, i)
			if (Twirl_TooltipTextRight1:IsShown()) then
				text = string.upper(Twirl_TooltipTextRight1:GetText())
				if (string.find(text, query, 1, true)) then
					return true
				end
			end
		else
			return false
		end
	end
end


-- Get the name of the itme in the specified bag and slot.
-- From Discord Macro Functions.
local function getItemName(bag, slot)
	local itemname, itemlink;
	if (slot) then
		itemlink = GetContainerItemLink(bag,slot);
	else
		itemlink = GetInventoryItemLink("player", bag);
	end
	if (itemlink) then
		itemname = string.sub(itemlink, string.find(itemlink, "[", 1, true) + 1, string.find(itemlink, "]", 1, true) - 1);
	end
	return itemname;
end


-- Returns the bag and slot location.  If dontuse is true, then don't actually
-- use the functon, just find it.
-- From Discord Macro Functions.
local function useItemByName(name, dontuse)
	local itemlink, itemname, itemid;
	for _, slot in SLOTS do
		itemid = GetInventorySlotInfo(slot);
		itemname = getItemName(itemid);
		if (itemname and string.find(itemname, name, 1, true)) then
			if (not dontuse) then
				UseInventoryItem(itemid);
			end
			return itemid, slot;
		end
	end
	for bag = 0,  4 do
		local bagslots = GetContainerNumSlots(bag);
		if (bagslots) then
			for slot = 1, bagslots do
				itemname = getItemName(bag, slot);
				if (itemname and string.find(itemname, name, 1, true)) then
					if (not dontuse) then
						UseContainerItem(bag, slot);
					end
					return bag, slot;
				end
			end
		end
	end
end


-- Returns how many items with the given name (string) are currently in the
-- player's inventory.
-- based on something from Discord Macro Functions (with modifications)
local function howManyItemByName(name)
	local itemname, itemcount
	local count = 0
	for bag = 0,  4 do
		local bagslots = GetContainerNumSlots(bag)
		if (bagslots) then
			for slot = 1, bagslots do
				itemname = getItemName(bag, slot)
				if (itemname and string.find(itemname, name, 1, true)) then
                    _, itemcount = GetContainerItemInfo(bag, slot)
                    count = count + itemcount
				end
			end
		end
	end
	return count
end



-- Equip item in the main hand.
-- from Discord Macro Functions.
local function equipMainHand(name)
	for bag = 0,  4 do
		local bagslots = GetContainerNumSlots(bag);
		if (bagslots) then
			for slot = 1, bagslots do
				itemname = getItemName(bag, slot);
				if (itemname and string.find(itemname, name, 1, true)) then
					PickupContainerItem(bag, slot);
					PickupInventoryItem(16);
					return;
				end
			end
		end
	end
end


-- Equip item in the off-hand.
-- from Discord Macro Functions
local function equipOffHand(name)
	for bag = 0,  4 do
		local bagslots = GetContainerNumSlots(bag);
		if (bagslots) then
			for slot = 1, bagslots do
				itemname = getItemName(bag, slot);
				if (itemname and string.find(itemname, name, 1, true)) then
					local _, _, locked = GetContainerItemInfo(bag, slot);
					if (not locked) then
						PickupContainerItem(bag, slot);
						PickupInventoryItem(17);
						return;
					end
				end
			end
		end
	end
end

-- Returns current form player is shapeshifted into,if any.
-- for instance"Bear Form", 1  or "Humanoid", O or "Cat Form", 2
-- from Discord Macro Functions
local function currentShapeshiftForm()
	for i=1,GetNumShapeshiftForms() do
		local _, name, isActive = GetShapeshiftFormInfo(i);
		if isActive == 1 then return name, i; end
	end
	return "Humanoid", 0;
end

-- Splits a string into a table of strings separated by 'separator'.
-- If 'separator' is not given, it is assumed to be " ".
-- e.g. splitString(aString, ", ") is the reverse of
-- table.concat(aTable, ", ")
-- from GFW_Utils.lua in Fizzwidget Guildentipper by Gazmik Fizzwidget (with 
-- modifications).
local function splitString(aString, separator)
	if (aString == nil) then return nil end
	if (not separator) then separator = " " end
	local t = {}
	local function helper(segment)
		table.insert(t, segment)
	end
	helper((string.gsub(aString, "(.-)"..separator, helper)))
	return t
end


-- how often to check timers.  1 = check each time _OnUpdate gets
-- called, 2 = check every other time, 3 = check every 3rd time, etc.
-- _OnUpdate gets called roughly once every 0.1s
TWIRL_TIMER_INTERVAL = 8


-- each time _OnUpdate gets called, this variable is decremented from
-- TWIRL_TIMER_INTERVAL till it reaches zero.  At zero, each entry in
-- both TIMERS and PERSISTENT_TIMERS tables is checked to see if is
-- time to call it's companion script.
local TIMER_COUNTDOWN = 0


-- Hold any active timers.  Persistent timers get saved on a per-character
-- basis between sessions.
TWIRL_PERSISTENT_TIMERS = {}
TWIRL_TIMERS = {}


-- Run script after delay (in seconds).  This will not work very well
-- for really small timer intervals (though you could tweak
-- TWIRL_TIMER_INTERVAL to change this).  script needs to be a string.
-- It will get evaluated when the timer expires.  example:
-- twirl.addTimer(120, [[twirl.print("I love spaghetti")]]) would output
-- "I love spaghetti" to the default chat frame in 20 minutes (120
-- seconds) from now.  Pesistent timers get saved between sessions on
-- a per-character basis.
local function addTimer(delay, script)
    table.insert(TWIRL_TIMERS, {startTime = time() + delay, script = script})
end

local function addPersistentTimer(delay,script)
    table.insert(TWIRL_PERSISTENT_TIMERS, {startTime = time() + delay, 
            script = script})
end


-- Check if the string unit is a valid unit.  Returns true  if valid,
-- otherwise returns false
-- example: twirl.isValidUnitDesignator("raid10") --> true
--          twirl.isValidUnitDesignator("player") --> true
--          twirl.isValidUnitDesignator("oogabooga") --> false
local function isValidUnitDesignator(unit)
    local found = false
    for _, u in pairs(UNITS) do
        if (u == unit) then
            found  = true
            break
        end
    end
    return found
end


-- Returns true if the player has the given spell (and optionally, rank), 
-- otherwise returns false.
local function playerHasSpell(name, rank)
    local i = 1
    local found = false
    repeat
        local n, r = GetSpellName(i, BOOKTYPE_SPELL)
        if (not n) then break end -- reached end of spellbook
        found = (name == n and (not rank or r == "Rank " .. rank))
        i = i + 1
    until (found)
    return found
end


-- casts spell on unit, return to current target and restarting 
-- auto-attack as appropriate
local function safeCastSpell(spell, unit)
	local oldTarget = UnitName("target")
	local wasAutoAttacking = twirl.playerAutoAttacking
	TargetUnit(unit)
    CastSpellByName(spell)
    if (oldTarget and unit ~= "target") then
    	TargetLastTarget()
	elseif (not oldTarget) then
    	ClearTarget()
	end
	if (wasAutoAttacking) then
    	debug("Attempting to restart auto-attack.")
    	twirl.addTimer(0.9,[[AttackTarget()]])
	end
end


function Twirl_OnUpdate()
    if (TIMER_COUNTDOWN <= 0) then
        TIMER_COUNTDOWN = TWIRL_TIMER_INTERVAL
        if (table.getn(TWIRL_TIMERS) > 0) then
            local currentTime = time()
            for key, item in pairs(TWIRL_TIMERS) do
                if (item.startTime < currentTime) then
                    RunScript(item.script)
                    table.remove(TWIRL_TIMERS,key) -- uhhh, is it safe to do
                end                                -- this? (threading)
            end
        end
        if (table.getn(TWIRL_PERSISTENT_TIMERS) > 0) then
            local currentTime = time()
            for key, item in pairs(TWIRL_PERSISTENT_TIMERS) do
                if (item.startTime < currentTime) then
                    RunScript(item.script)
                    table.remove(TWIRL_PERSISTENT_TIMERS,key)
                end
            end
        end
    else TIMER_COUNTDOWN = TIMER_COUNTDOWN - 1 end
end


function Twirl_OnEvent()
    if (event == "PLAYER_ENTER_COMBAT") then
        twirl.playerAutoAttacking = true
    elseif (event == "PLAYER_LEAVE_COMBAT") then
        twirl.playerAutoAttacking = false
    end
end


-- exported functions
twirl = {
    addPersistentTimer = addPersistentTimer,
    castPetActionByName = castPetActionByName,
    addTimer = addTimer,
    colorize = colorize,
    colors = COLORS,
    checkForBuff = checkForBuff,
    checkForBuffType = checkForBuffType,
    checkForDebuff = checkForDebuff,
    checkForDebuffType = checkForDebuffType,
    currentShapeshiftForm = currentShapeshiftForm,
    equipMainHand = equipMainHand,
    equipOffHand = equipOffHand,
    getItemName = getItemName,
    howManyItemByName = howManyItemByName,
    isValidUnitDesignator = isValidUnitDesignator,
    playerAutoAttacking = false, -- value gets changed by Twirl_OnEvent()
    playerHasSpell = playerHasSpell,
    message = message,
    messager = messager,
    print = print,
    printer = printer,
	safeCastSpell = safeCastSpell,
    splitString = splitString,
    useItemByName = useItemByName}